package com.googlecode.yoohoo.io.socket;

import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.mina.common.ByteBuffer;
import org.apache.mina.common.IoAcceptor;
import org.apache.mina.common.IoHandler;
import org.apache.mina.common.SimpleByteBufferAllocator;
import org.apache.mina.common.ThreadModel;
import org.apache.mina.filter.LoggingFilter;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.executor.ExecutorFilter;
import org.apache.mina.transport.socket.nio.SocketAcceptor;
import org.apache.mina.transport.socket.nio.SocketAcceptorConfig;
import org.osgi.framework.BundleContext;

import com.googlecode.yoohoo.config.IConfigManager;
import com.googlecode.yoohoo.io.socket.security.TlsEnvironment;
import com.googlecode.yoohoo.osgi.IBundleContextAware;
import com.googlecode.yoohoo.utils.AppUtils;
import com.googlecode.yoohoo.xmppcore.connection.IConnectionHandler;
import com.googlecode.yoohoo.xmppcore.io.IConnector;

public class Connector implements IConnector, IBundleContextAware {
	private static final Log log = LogFactory.getLog(Connector.class);
	private static final String FILTER_NAME_CODEC = "codec";
	private static final String FILTER_NAME_LOGGER = "logger";
	private static final String FILEER_NAME_EXECUTOR = "socket.executor.thread.pool";
	
	private IConnectionHandler connectionHandler;
	private static IoAcceptor acceptor;
	private BundleContext bundleContext;
	private ExecutorService executorService;
	
	private IConfigManager configManager;
	
	@Override
	public void initialize(IConnectionHandler connectionHandler) throws Exception {
		this.connectionHandler = connectionHandler;
		configManager = AppUtils.getConfigManager(bundleContext);
		if (configManager == null) {
			if (log.isWarnEnabled()) {
				log.warn("Failed to start socket connector. Can't fetch server config manager");
			}
			
			return;
		}
		
		if (configManager.getBoolean(ConfigManager.KEY_TLS_REQUIRED)) {
			TlsEnvironment tlsEnvironment = new TlsEnvironment(AppUtils.getAppHome(bundleContext),
					AppUtils.getHostName(bundleContext));
			
			if (!tlsEnvironment.ready()) {
				tlsEnvironment.initialize();
			}
		}
		
		bindSocketAcceptor();
		
		if (log.isDebugEnabled()) {
			log.debug("Socket Connector initialized.");
		}
	}
	
	private void bindSocketAcceptor() throws IOException {
		ByteBuffer.setUseDirectBuffers(false);
		ByteBuffer.setAllocator(new SimpleByteBufferAllocator());
		acceptor = new SocketAcceptor(Runtime.getRuntime().availableProcessors() + 1,
				Executors.newCachedThreadPool());
		SocketAcceptorConfig config = new SocketAcceptorConfig();
		config.setThreadModel(ThreadModel.MANUAL);
		
		if (configManager.getBoolean("logging.mina")) {
			if (log.isDebugEnabled()) {
				log.debug("Mina protocol events will be logged.");
			}
			
			config.getFilterChain().addLast(FILTER_NAME_LOGGER, new LoggingFilter());
		}
		
		config.getFilterChain().addLast(FILTER_NAME_CODEC, new ProtocolCodecFilter(
				XmppEncoder.class, XmppDecoder.class));
		executorService = Executors.newCachedThreadPool();
		config.getFilterChain().addLast(FILEER_NAME_EXECUTOR, new ExecutorFilter(executorService));
		
		int port = configManager.getInt("server.port");
		IoHandler handler = new ConnectionAdaptor(connectionHandler, bundleContext);
		acceptor.bind(new InetSocketAddress(port), handler, config);
		
		log.info(String.format("Socket acceptor binded on port %d.", port));
	}



	@Override
	public void destroy() throws Exception {
		if (log.isDebugEnabled()) {
			log.debug("Socket Connector destroyed.");
		}
	}

	@Override
	public void setBundleContext(BundleContext bundleContext) {
		this.bundleContext = bundleContext;
	}

}
